﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Data.Imaging;
using RayDen.Library.Entity;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Scene;
using RayDen.Library.Entity.SceneGraph;
using RayDen.RayEngine.Core.Components;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Library;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.IntersectionEngines;

//;//BasicPerspectiveCamera;//FiniteAppertureCamera

namespace RayDen.RayEngine.Scene
{
    public class SceneBuilder
    {
        public Dictionary<string, ILight> Lights;
        public List<MaterialInfo> MaterialInfos;
        public List<CameraInfo> Cameras;
        public List<VolumeMaterialInfo> VolumeMats;

        public SceneGeometryInfo SceneGeo;

        public BaseInfiniteLight EnvironmentMap;
        public OptimizedTextureAtlas Textures;
        public Dictionary<TextureType,int>[] texturesMap;

        public FrameMaterialBuilder FrameMaterialBuilder;


        protected List<INodeVisitor> visitors;

        public SceneGraphService SGService { get; set; }

        public void Setup()
        {
            Lights = new Dictionary<string, ILight>();
            MaterialInfos = new List<MaterialInfo>();
            Cameras = new List<CameraInfo>();
            VolumeMats = new List<VolumeMaterialInfo>();
            visitors = new List<INodeVisitor>
                {
                    CreateVisitor<MaterialsBuilder>(),
                    CreateVisitor<LightBuilder>(),
                    CreateVisitor<CameraBuilder>(),
                    CreateVisitor<GeoDataBuilder>(),
                    CreateVisitor<VolumeBuilder>(),
                };
        }

        public void UpdateScene(RayEngineScene scene, SceneGraphInfo updatedSceneGraph, SceneGraphChangesType changes)
        {
            if (changes.HasFlag(SceneGraphChangesType.Observer))
            {
                updatedSceneGraph.Visit(visitors.SingleOrDefault(item => item is CameraBuilder));
                scene.Camera = new BasicPerspectiveCamera((Point)Cameras.First().Position, Cameras.First().Direction,
                                          Cameras.First().Up, updatedSceneGraph.Parameters.Get<int>("ImageWidth"), updatedSceneGraph.Parameters.Get<int>("ImageHeight"),Cameras.First().Fov);
            }
            if (changes.HasFlag(SceneGraphChangesType.Geometry))
            {
                updatedSceneGraph.Visit(visitors.SingleOrDefault(item => item is GeoDataBuilder));
                scene.sceneData = new SceneDataInfo(this.SceneGeo);
                scene.SceneGeometry = SceneGeo;
                scene.BuildSceneData();
            }
            if (changes.HasFlag(SceneGraphChangesType.Material))
            {
                updatedSceneGraph.Visit(visitors.SingleOrDefault(item => item is MaterialsBuilder));
                LoadTextures(MaterialInfos,this.MaterialInfos.Count);
                scene.MaterialProvider = new MaterialInfoContainer(new MaterialInfo(), this.MaterialInfos);
                scene.texturesMap = texturesMap;
                scene.EnvironmentMap = EnvironmentMap;
                scene.OptTextures = this.Textures;

            }
            if (changes.HasFlag(SceneGraphChangesType.Lighting))
            {
                updatedSceneGraph.Visit(visitors.SingleOrDefault(item => item is LightBuilder));
                scene.Lights = this.Lights.Values.ToArray();
                scene.EnvironmentMap = EnvironmentMap;
            }
        }



        public RayEngineScene OpenFrame(IRayDenLibraryFrame frame, bool areaLightsAsMesh)
        {
            var scene = new RayEngineScene();

            if (SGService == null)
                SGService = new SceneGraphService();
            SGService.OpenFrame((FrameDescription)frame);
            var sceneGraph = SGService.SceneGraph;
            visitors.ForEach(item => ((DependentComponentBase)item).Populate(sceneGraph));
            foreach (var nodeVisitor in visitors)
            {
                sceneGraph.Visit(nodeVisitor);
            }

            var materialProvider = new MaterialInfoContainer(new MaterialInfo(), this.MaterialInfos);
            scene.MaterialProvider = materialProvider;

            var frameMaterials = frame.Elements.Where(item => item is FrameMaterialInfo);
            var materials = frameMaterials as IFrameElement[] ?? frameMaterials.ToArray();
            if (materials.Any())
            {
                FrameMaterialBuilder = new FrameMaterialBuilder(materials.Cast<FrameMaterialInfo>().ToArray());
                this.MaterialInfos.AddRange(FrameMaterialBuilder.GetMaterialInfos());
            }

            LoadTextures(this.MaterialInfos, this.MaterialInfos.Count);


            Tracer.TraceLine("Total textures memory : {0:F5} Mbytes", ImageFactory.MemoryOccupied / (1024.0f*1024.0f));



                //new DefaultMaterialContainer(new MaterialInfo(), this.MaterialInfos);
            scene.AreaLightAsMesh = areaLightsAsMesh;
            scene.Lights = this.Lights.Values.ToArray();
            scene.sceneData = new SceneDataInfo(this.SceneGeo);
            foreach (var triangleMeshInfo in scene.sceneData.Meshes)
            {
                triangleMeshInfo.MaterialID = materialProvider.GetMaterialID(triangleMeshInfo.MaterialName);
            }

            scene.SceneGeometry = SceneGeo;
            scene.BuildSceneData();
            scene.VolumeMats = this.VolumeMats;
            scene.texturesMap = texturesMap;

            scene.EnvironmentMap = EnvironmentMap;
            scene.OptTextures = this.Textures;


          


            var width = frame.Get<int>("ImageWidth");
            var height = frame.Get<int>("ImageHeight");

            //scene.Camera = new EnvironmentCamera((Point)Cameras.First().Position, width , height);
            
            scene.Camera = Cameras.First().Pinhole ? 
                (BaseCamera) new  BasicPerspectiveCamera((Point)Cameras.First().Position, Cameras.First().Direction,Cameras.First().Up, width, height, Cameras.First().Fov)
                : new FiniteAppertureCamera((Point)Cameras.First().Position, Cameras.First().Direction, Cameras.First().Up, width, height, Cameras.First().Fov) { lensRadius = Cameras.First().LensRadius};
            
            if (GlobalConfiguration.Instance.UseSceneCaching)
            {
                var cacheFileName = string.Format("{0}{1}.cache", GlobalConfiguration.Instance.SceneCacheDirectory, frame.FrameName);
                if (File.Exists(cacheFileName))
                {
                    scene.Cache = SceneCache.Load(cacheFileName);
                }
                else
                {
                    
                    Tracer.TraceLine("Building bvh cache");
                    var da = new BvhDataAdapter(scene);
                    var cache = new SceneCache {BvhData = da.BuildData()};
                    cache.Save(cacheFileName);
                    scene.Cache = cache;
                }
            }
            return scene;
        }

        public void ProcessMaterial(int mID, MaterialInfo materialInfo)
        {
            var textures = new Dictionary<TextureType, int>();


            if (materialInfo.Diffuse != null)
            {
                ProcessMaterialTexture((ImageTextureInfo)materialInfo.Diffuse, textures, TextureType.Diffuse);
            }

            if (materialInfo.BumpMap != null)
            {
                ProcessMaterialTexture((ImageTextureInfo)materialInfo.BumpMap, textures, TextureType.Bump);
            }

            if (materialInfo.NormalMap != null)
            {
                ProcessMaterialTexture((ImageTextureInfo)materialInfo.NormalMap, textures, TextureType.Normal);
            }

            if (materialInfo.Alpha != null && materialInfo.AlphaTexture is ImageTextureInfo)
            {
                ProcessMaterialTexture((ImageTextureInfo) materialInfo.AlphaTexture, textures, TextureType.Alpha);
            }

            if (materialInfo.Specular != null)
            {
                ProcessMaterialTexture((ImageTextureInfo)materialInfo.Specular, textures, TextureType.Reflection);
            }


            if (textures.Any())
            {
                if (texturesMap.Length > mID)
                {
                    texturesMap[mID] = textures;
                }
                else
                {
                    Array.Resize(ref texturesMap, texturesMap.Length*2);
                    texturesMap[mID] = textures;
                }
            }
        }


        private TVisitor CreateVisitor<TVisitor>(params object[] deps)
            where TVisitor : DependentComponentBase, INodeVisitor, ISceneBuilder, new()
        {
            var vis = new TVisitor();
            vis.Populate(deps);
            vis.Builder = this;
            return vis;
        }

        private void LoadTextures(IEnumerable<MaterialInfo> matInfos, int matsCount)
        {
            Tracer.TraceLine("Start loading textures");
            texturesMap = new Dictionary<TextureType, int>[matsCount];
            this.Textures = new OptimizedTextureAtlas();


            int materialIndex = 0;
            foreach (var materialInfo in matInfos)
            {
                var textures = new Dictionary<TextureType, int>();

                if (materialInfo.Diffuse != null)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.Diffuse, textures, TextureType.Diffuse);
                }

                if (materialInfo.BumpMap != null)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.BumpMap, textures, TextureType.Bump);
                }

                if (materialInfo.NormalMap != null)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.NormalMap, textures, TextureType.Normal);
                }

                if (materialInfo.Alpha != null && materialInfo.AlphaTexture is ImageTextureInfo)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.AlphaTexture, textures, TextureType.Alpha);
                }

                if (materialInfo.Specular != null)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.Specular, textures, TextureType.Reflection);
                }
                /*
            else
            {
                materialInfo.BumpMap = new SolidTextureInfo() {Name = "noise"};
                this.Textures.AddTexture(new SolidTextureInfo()
                {
                    Name = "noise"
                });
                textures.Add("noise");
            }*/
              
                if (textures.Any())
                    texturesMap[materialIndex] = textures;
                materialIndex++;

            }

            Tracer.TraceLine("Complete loading textures");
        }

        private void ProcessMaterialTexture(ImageTextureInfo diff, Dictionary<TextureType, int> textures, TextureType type)
        {
            try
            {
                var texPath = this.FindTexture(diff);
                if (texPath != null)
                {
                    var id = this.Textures.AddTexture(new ImageTextureInfo()
                        {
                            FilePath = texPath,
                            Name = Path.GetFileName(texPath)
                        });
                    textures.Add(type, id);
                }
                else
                {
                    Console.WriteLine("Texture {0} not found!", diff.FilePath);
                }
            }
            catch (Exception ex)
            {
                Tracer.TraceLine("Couldnt load texture {0}", diff.FilePath);
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);
            }
        }

        public string FindTexture(ImageTextureInfo ii)
        {
            string filePath = ii.FilePath;
            if (File.Exists(filePath))
                return filePath;
            if (File.Exists(ii.Name))
            {
                return Directory.GetCurrentDirectory() + @"\" + ii.Name;
            }
            string[] textureDirs = GlobalConfiguration.Instance.Get("Renderer.TexturesPaths", new string[0]);
            return (from textureDir in textureDirs.Select(item => item.Trim('\r', '\n', ' ')) where File.Exists(textureDir + "\\" + ii.FilePath) select textureDir + "\\" + ii.FilePath).FirstOrDefault();
        }
    }
}